﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using Netfraction.Common.DataStorage;
using Netfraction.Hub.Scripting;

namespace NetfractionHub.Security
{
	public class AccessPermissionBase
	{
		private AccessState _state;

		public AccessPermissionBase()
		{
			_state = AccessState.Deny;
		}

		public AccessPermissionBase(AccessState state)
		{
			_state = state;
		}

		/*public static bool operator ==(AccessPermissionBase a, AccessPermissionBase b)
		{
			return (a.GetType() == b.GetType() && a.State == b.State);
		}

		public static bool operator !=(AccessPermissionBase a, AccessPermissionBase b)
		{
			return !(a.GetType() == b.GetType() && a.State == b.State);
		}

		public override bool Equals(object obj)
		{
			if (obj is AccessPermissionBase)
			{
				return (obj.GetType() == this.GetType() && (obj as AccessPermissionBase).State == this.State);
			}
			else
			{
				return false;
			}
		}*/

		public string ToXml()
		{
			XmlDocument xDoc = new XmlDocument();
			XmlElement xElem = xDoc.CreateElement("SecurityPermission");
			XmlAttribute xAttrib = xDoc.CreateAttribute("name");
			xAttrib.Value = this.GetType().ToString();
			xElem.Attributes.Append(xAttrib);
			xAttrib = xDoc.CreateAttribute("access");
			xAttrib.Value = this.State.ToString().ToLowerInvariant();
			xElem.Attributes.Append(xAttrib);
			return xElem.OuterXml;
		}

		public static Dictionary<int, AccessPermissionBase> LoadPermissions()
		{
			Dictionary<int, AccessPermissionBase> perms = new Dictionary<int, AccessPermissionBase>();

			List<PermissionSourceData> psData = PermissionSourceData.LoadPermissionSourceData();
			if (psData == null)
				return null;

			foreach (PermissionSourceData psd in psData)
			{
				try
				{
					perms.Add(psd.PermissionId, PermissionBuilder.Create(psd));
				}
				catch (ArgumentNullException ane)
				{
					Console.WriteLine(ane.Message);
				}
			}
			return perms;
		}

		public static AccessPermissionBase LoadPermissionById(int permissionId)
		{
			PermissionSourceData psData = PermissionSourceData.LoadPermissionSourceDataById(permissionId);
			if (psData == null)
				return null;

			try
			{
				return PermissionBuilder.Create(psData);
			}
			catch (ArgumentNullException ane)
			{
				Console.WriteLine(ane.Message);
				return null;
			}
		}

		public static AccessPermissionBase LoadPermissionByName(string fullName)
		{
			PermissionSourceData psData = PermissionSourceData.LoadPermissionSourceDataByName(fullName);
			if (psData == null)
				return null;

			try
			{
				return PermissionBuilder.Create(psData);
			}
			catch (ArgumentNullException ane)
			{
				Console.WriteLine(ane.Message);
				return null;
			}
		}

		internal const string PERMISSIONS_DATABASE_NAME = "permissions.db";

		internal static void CreateDatabase(SqlDataStore _sqlStore)
		{
			//_sqlStore.ExecuteNonQuery("DROP TABLE Permissions");
			if (!_sqlStore.TableExists("Permissions"))
			{
				_sqlStore.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS Permissions (PermissionId INTEGER PRIMARY KEY AUTOINCREMENT, DisplayName TEXT, FullTypeName TEXT, CodeSource INTEGER)");
				foreach (Type t in GetInternallyDefinedPermissions())
				{
					RegisterPermissionInternal(_sqlStore, t);
				}
			}
			if (!_sqlStore.TableExists("AccessState"))
			{
				_sqlStore.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS AccessState (StateId INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT)");
				_sqlStore.ExecuteNonQuery("INSERT INTO AccessState (Name) VALUES ('Allow')");
				_sqlStore.ExecuteNonQuery("INSERT INTO AccessState (Name) VALUES ('Deny')");
			}

			//_sqlStore.ExecuteNonQuery("INSERT INTO Permissions (DisplayName, FullTypeName, CodeSource) VALUES ('SetHubName', 'NetfractionHub.Security.Permissions.SetHubNamePermission', 1)");
			//_sqlStore.ExecuteNonQuery("INSERT INTO Permissions (DisplayName, FullTypeName, CodeSource) VALUES ('SampleAccess', 'TestPlugin.SampleAccessPermission', 2)");
		}

		private static List<Type> GetInternallyDefinedPermissions()
		{
			List<Type> classes = new List<Type>();
			Assembly a = Assembly.GetExecutingAssembly();
			foreach (Type t in a.GetTypes())
			{
				if (t.Namespace == "NetfractionHub.Security.Permissions" && t.IsSubclassOf(typeof(AccessPermissionBase)))
					classes.Add(t);
			}
			return classes;
		}

		public static void RegisterPermissions(PermissionSourceData source, List<Type> types)
		{
			foreach (Type t in types)
			{
				try
				{
					RegisterPermission(t, source);
				}
				catch (ArgumentException ae)
				{
					Console.WriteLine(ae.Message);
				}
			}
		}

		public static bool RegisterPermission(Type t, PermissionSourceData source)
		{
			if (!t.IsSubclassOf(typeof(AccessPermissionBase)))
				throw new ArgumentException(String.Format(System.Globalization.CultureInfo.CurrentCulture, "Type '{0}' is not a subclass of AccessPermissionBase.", t.FullName));

			SqlDataStore _sqlStore = new SqlDataStore(SecurityDataSource.Source, AccessPermissionBase.PERMISSIONS_DATABASE_NAME);

			AccessPermissionBase.CreateDatabase(_sqlStore);

			int codeSourceId = CodeSource.GetCodeSourceId(source.Source);
			if (codeSourceId == 0)
			{
				if (CodeSource.RegisterCodeSource(source.Source))
				{
					codeSourceId = CodeSource.GetCodeSourceId(source.Source);
					if (codeSourceId == 0)
						return false;
				}
				else
				{
					return false;
				}
			}

			int affectedRows = RegisterPermissionInternal(_sqlStore, t, codeSourceId);

			_sqlStore.Disconnect();

			return affectedRows == 1;
		}

		public static bool RegisterPermission(AccessPermissionBase perm, PermissionSourceData source)
		{
			SqlDataStore _sqlStore = new SqlDataStore(SecurityDataSource.Source, AccessPermissionBase.PERMISSIONS_DATABASE_NAME);

			AccessPermissionBase.CreateDatabase(_sqlStore);

			int codeSourceId = CodeSource.GetCodeSourceId(source.Source);
			if (codeSourceId == 0)
			{
				if (CodeSource.RegisterCodeSource(source.Source))
				{
					codeSourceId = CodeSource.GetCodeSourceId(source.Source);
					if (codeSourceId == 0)
						return false;
				}
				else
				{
					return false;
				}
			}

			int affectedRows = RegisterPermissionInternal(_sqlStore, perm.ToString(), perm.GetType().FullName, codeSourceId);

			_sqlStore.Disconnect();

			return affectedRows == 1;
		}

		[SqlVulnerabilityAttribute("Potential SQL Injection flaw.")]
		private static int RegisterPermissionInternal(SqlDataStore _sqlStore, string displayName, string fullName, int codeSourceId)
		{
			object retVal = _sqlStore.ExecuteScalar(String.Format(System.Globalization.CultureInfo.InvariantCulture,
				"SELECT count() FROM Permissions WHERE DisplayName='{0}' AND FullTypeName='{1}' AND CodeSource={2}",
				displayName,
				fullName,
				codeSourceId));

			if (retVal != null)
			{
				// check if permission already registered - not sure why retVal can be either Int32 or Int64, but need to handle it
				if (retVal is Int64 && SqlDataStore.CastInt64ToInt((Int64)retVal) > 0)
					return 0;
				else if (retVal is Int32 && (Int32)retVal > 0)
					return 0;
			}
			else
			{
				return 0;
			}

			return _sqlStore.ExecuteNonQuery(String.Format(System.Globalization.CultureInfo.InvariantCulture,
					"INSERT INTO Permissions (DisplayName, FullTypeName, CodeSource) VALUES ('{0}', '{1}', {2})",
					displayName,
					fullName,
					codeSourceId));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="_sqlStore"></param>
		/// <param name="t">MUST refer to a type that inherits AccessPermissionBase. This function does not verify the fact, but failure to do so will cause database corruption.</param>
		private static int RegisterPermissionInternal(SqlDataStore _sqlStore, Type t, int codeSourceId)
		{
			return RegisterPermissionInternal(_sqlStore, Activator.CreateInstance(t).ToString(), t.FullName, codeSourceId);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="_sqlStore"></param>
		/// <param name="t">MUST refer to a type that inherits AccessPermissionBase. This function does not verify the fact, but failure to do so will cause database corruption.</param>
		private static int RegisterPermissionInternal(SqlDataStore _sqlStore, Type t)
		{
			// We take the liberty of assuming the CodeSourceId is 1. The first CodeSource in the database should be for internal types.
			return RegisterPermissionInternal(_sqlStore, t, 1);
		}

		public static int GetPermissionId(Type t)
		{
			return GetPermissionId(t.FullName);
		}

		[SqlVulnerabilityAttribute("Potential SQL Injection flaw.")]    // could it be resolved by only allowing public access via GetPermissionId(Type)?
		public static int GetPermissionId(string fullName)
		{
			SqlDataStore _sqlStore = new SqlDataStore(SecurityDataSource.Source, PERMISSIONS_DATABASE_NAME);
			CreateDatabase(_sqlStore);

			using (Community.CsharpSqlite.SQLiteClient.SqliteDataReader sdr = _sqlStore.ExecuteQuery(String.Format(System.Globalization.CultureInfo.InvariantCulture, "SELECT PermissionId FROM Permissions WHERE FullTypeName='{0}' LIMIT 1", fullName)))
			{
				if (sdr != null)
				{
					while (sdr.Read())
					{
						try
						{
							return sdr.GetInt32(0);
						}
						catch (ArgumentException ae)
						{
							Console.WriteLine(ae.Message);  // sdr.GetString(0) is an empty string, or not a named constant in the enum
						}
						catch (InvalidCastException ice)
						{
							Console.WriteLine(ice.Message);
						}
						catch (Exception e)
						{
							Console.WriteLine(e.Message);
						}
					}
				}
			}
			//_sqlStore.Disconnect();

			return 0;
		}

		public AccessState State
		{
			get { return _state; }
			set { _state = value; }
		}
	}
}